Skip to content

feat(lint,cli): flag flow update_record writes to readonly fields at design time (#3425)#3465

Merged
os-zhuang merged 1 commit into
mainfrom
feat/3425-readonly-flow-lint
Jul 24, 2026
Merged

feat(lint,cli): flag flow update_record writes to readonly fields at design time (#3425)#3465
os-zhuang merged 1 commit into
mainfrom
feat/3425-readonly-flow-lint

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What & why

Closes the design-time half of #3425 (D1 + D2 from the triage plan).

A flow update_record node that writes a field the target object declares readonly: true, under the default runAs: 'user', is a silent no-op: the objectql engine strips static-readonly fields from a non-system UPDATE payload (#2948), so the intended write never lands — yet the step still reports success. #3407/#3413 made the strip observable at run time (step warning + droppedFields); this PR shifts discovery left to os validate / os build, so an author finds the mismatch at design time instead of by reading server WARN logs days later.

Decision recorded on the issue

readonly semantics are kept as-is and formalized, not loosened to exempt the flow engine (that would turn any user-triggerable flow into a readonly-bypass gadget and undo #2948). The formal contract:

Writer static readonly readonlyWhen
REST/UI, runAs:'user' flow (default) stripped stripped when predicate TRUE
runAs:'system' flow, system hooks, seeds written written
INSERT (all callers, engine path) exempt exempt

"Users can't edit this, but automation maintains it" = declare the field readonly and run the maintaining flow runAs:'system'.

Changes

  • New rule validateReadonlyFlowWrites (@objectstack/lint) — pure (stack) => Finding[] (ADR-0019). Static readonly + literal field under runAs !== 'system'error (gates); readonlyWhenwarning (advisory). Deliberately narrow to be false-positive-free: skips create_record (INSERT is engine-exempt from the strip), runAs:'system' flows, templated object names, and non-literal fields maps.
  • Wired into os validate and os compile/os build, mirroring the security-posture gate (errors fail; advisories print dimmed).
  • Docs — the contract table + "use runAs:'system'" guidance added to the objectstack-data and objectstack-automation skills.

Verification

  • 16 unit tests (positive + every "must-not-flag" boundary). Red-proof: neutering the rule fails exactly the 8 positive tests.
  • Full @objectstack/lint suite green (283); DTS build clean; edited CLI files typecheck clean.
  • No false positives on app-crm, app-showcase, app-todo (os validate passes).
  • End-to-end red: pointing app-crm's crm_convert_lead_wizard (runAs:'user') update_record at crm_opportunity.approval_status makes os validate fail with Readonly flow-write check failed … flows[0].nodes[6].config.fields.approval_status (reverted).

Out of scope (tracked separately)

🤖 Generated with Claude Code

…design time (#3425)

A flow `update_record` node that writes a field the target object declares
`readonly: true`, under the default `runAs: 'user'`, is a silent no-op: the
objectql engine strips static-`readonly` fields from a non-system UPDATE
payload (#2948) so the write never lands, while the step still reports success.
#3407/#3413 surfaced the strip at run time; this shifts discovery left to
`os validate` / `os build`.

- New `@objectstack/lint` rule `validateReadonlyFlowWrites`: static readonly +
  literal field under runAs!=='system' → error (gates); readonlyWhen → warning.
  Skips create_record (INSERT is engine-exempt), runAs:'system' flows, templated
  object names, and non-literal fields maps to stay false-positive-free.
- Wired into `os validate` and `os compile`/`os build` (mirrors the security
  posture gate). Verified: reds on a violating app-crm flow, clean on all
  example apps.
- Documents the formal contract in the objectstack-data / -automation skills:
  readonly governs the user/API surface; system writers (runAs:'system', hooks,
  seeds) maintain it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 24, 2026 3:33pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/lint.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant